﻿using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Web;
using Altairis.MailToolkit.Transport;

namespace Altairis.MailToolkit {

    public class PickupHandler : IHttpHandler {
        private SmtpClientTransportProvider provider;
        private string packageFileName;

        public PickupHandler() {
            var providers = TransportManager.Providers.OfType<SmtpClientTransportProvider>().Where(x => x.HandlerEnabled).ToArray();
            if (providers.Length == 1) {
                this.provider = providers[0];
                this.packageFileName = Path.Combine(provider.CustomPickupDirectory, "MailPackage.zip");
            }
        }

        public bool IsReusable {
            get { return true; }
        }

        public void ProcessRequest(HttpContext context) {
            // Check if we have smtp provider with enabled handler
            if (this.provider == null) throw new HttpException(400, "Bar Request - Invalid configuration, single SmtpClientTransportProvider must be configured with handlerEnabled=true to use this handler.");

            // Check if the client IP is allowed
            if (!this.AuthorizeClientIp(context.Request.UserHostAddress)) {
                context.Response.StatusCode = 403;
                context.Response.StatusDescription = "Forbidden - IP address blocked";
                return;
            }

            // Check Authorization header
            var authHeader = context.Request.Headers["Authorize"];
            if (string.IsNullOrWhiteSpace(authHeader) || !this.AuthorizeRequest(authHeader)) {
                context.Response.StatusCode = 401;
                context.Response.StatusDescription = "Unauthorized - Authorization header missing or invalid";
                return;
            }

            if (context.Request.HttpMethod.Equals("DELETE", StringComparison.OrdinalIgnoreCase)) {
                this.DeletePackage(context);
            }
            else if (context.Request.HttpMethod.Equals("GET", StringComparison.OrdinalIgnoreCase)) {
                this.SendPackage(context);
            }
            else {
                context.Response.StatusCode = 400;
                context.Response.StatusDescription = "Bad Request - Unsupported verb";
                return;
            }
        }

        // Helper methods

        private void SendPackage(HttpContext context) {
            if (context == null) throw new ArgumentNullException("context");

            // Get new messages
            var queueFolder = new DirectoryInfo(this.provider.CustomPickupDirectory);
            var emlFiles = queueFolder.GetFiles("*.eml");
            if (emlFiles.Length > 0) {
                using (var zip = ZipFile.Open(packageFileName, ZipArchiveMode.Update)) {
                    foreach (var fi in emlFiles) {
                        zip.CreateEntryFromFile(fi.FullName, fi.Name, CompressionLevel.Fastest);
                        fi.Delete();
                    }
                }
            }

            // Send package or empty response
            if (!File.Exists(packageFileName)) {
                context.Response.StatusCode = 204;
                context.Response.StatusDescription = "No Content";
            }
            else {
                context.Response.ContentType = "application/octet-stream";
                context.Response.Headers.Add("Content-Disposition", "attachment; filename=\"MailPackage.zip\"");
                context.Response.TransmitFile(packageFileName);
            }
        }

        private void DeletePackage(HttpContext context) {
            if (context == null) throw new ArgumentNullException("context");

            File.Delete(this.packageFileName);
            context.Response.StatusCode = 204;
            context.Response.StatusDescription = "No Content - deleted";
        }

        private bool AuthorizeRequest(string authorizeHeader) {
            // Validate arguments
            if (authorizeHeader == null) throw new ArgumentNullException("authorizeHeader");
            if (string.IsNullOrWhiteSpace(authorizeHeader)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "authorizeHeader");

            // Parse header
            var authData = authorizeHeader.Split(';');
            if (authData.Length != 3) return false; // invalid format
            if (!authData[0].Equals("HMACSHA512")) return false;  // unknown or unsupported auth

            // Get timestamp
            DateTime dt;
            if (!DateTime.TryParseExact(authData[1], "s", null, System.Globalization.DateTimeStyles.None, out dt)) return false; // Invalid date time
            var delta = Math.Abs(DateTime.UtcNow.Subtract(dt).TotalSeconds);
            if (delta > provider.HandlerTimeWindow) return false; // Too much clock skew

            // Get mac
            byte[] usedMac;
            try {
                usedMac = Convert.FromBase64String(authData[2]);
            }
            catch (Exception) {
                return false; // Invalid Base64
            }

            // Compute correct mac
            using (var hmac = new System.Security.Cryptography.HMACSHA512(this.provider.HandlerSecret)) {
                var correctMac = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(authData[1]));
                return correctMac.SequenceEqual(usedMac);
            }
        }

        private bool AuthorizeClientIp(string ip) {
            // Validate arguments
            if (ip == null) throw new ArgumentNullException("ip");
            if (string.IsNullOrWhiteSpace(ip)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "ip");

            // If not configured, allow all
            if (this.provider.HandlerIpAddresses == null || this.provider.HandlerIpAddresses.Length == 0) return true;

            // Check if IP is in allowed list
            return this.provider.HandlerIpAddresses.Contains(ip);
        }
    }
}